建明與永仁於天台相見,不料國平也趕到。永仁事先已報警,想持槍壓著建明到樓下交予警方。不料,於進電梯時被國平擊斃,原來他也是韓琛安裝於警隊的臥底。國平向建明表明身份,希望之後一起合作。但最終建明選擇於電梯中殺死國平,並營造永仁與國平雙雙死於槍戰的假象。事後,心兒於葉校長遺物中發現永仁臥底檔案,恢復其警察身份,並由建明代表行禮。
本幕需要特別注意global
current_user_id
所屬object
的PoliceRank
是否合乎access policy
!
global
current_user_id
由於本場景需要多次操作PoliceSpy object
,我們可以insert
一個PoliceRank
為DCP
的Police object
,並將其id
指定給global current_user_id
。
insert Police {name:= "test_DCP", police_rank:=PoliceRank.DCP};
set global current_user_id:= (
select Police
filter .police_rank=PoliceRank.DCP limit 1).id;
update
chen
將永仁的經典台詞加入到classic_lines property
中。
update chen
set {
classic_lines := .classic_lines ++ ["對唔住,我係差人。"],
};
update
lau
將建明的經典台詞加入到classic_lines property
中。
update lau
set {
classic_lines := ["我以前無得揀,我而家想做好人。"],
};
insert
ChenLauContact
這是本劇中,兩人最後一次聯絡了。
insert ChenLauContact {
how:= "面對面",
detail:= "建明與永仁相約於天台上談判",
`when`:= (insert FuzzyTime {
fuzzy_year:=2002,
fuzzy_month:=11,
fuzzy_day:=27,
fuzzy_hour:=15,
fuzzy_minute:=0,
fuzzy_second:=0,
}),
where:= (select Location filter .name="天台"),
};
insert
真.林國平真沒想到,國平竟然也是韓琛的臥底,第一次看到這段時,真是驚訝不已!
可是這麼一來,國平就不應該是Police
而是GangsterSpy
囉?我們應該刪掉國平 Police object
,並新增一個國平 GangsterSpy object
嗎?
這樣的話,之前國平 Police object
的相關記錄都會被刪除(例如:CIBTeamTreat
),這樣合理嗎?又或者我們應該重新去確認所有跟國平 Police object
有關的object
將其替換為國平 GangsterSpy object
?
該怎麼做其實沒有標準的答案,不過一個比較常見的方法是使用soft delete
。使用一個類似is_active
的property
來表達該object
的存取狀態,而不真正將其從資料庫中刪除。畢竟在最後一幕之前,我們的確不知道國平是臥底,國平 Police object
是一個合適的表達。
最後我們insert
國平的GangsterSpy object
如下:
with b:= assert_single((select Police filter .name="林國平"))
insert GangsterSpy {
name:= b.name,
nickname:= b.nickname,
police_rank:= b.police_rank,
gangster_boss:= hon,
dept:= b.dept,
actors:= b.actors
};
在緊湊的臥底對決中,其實導演與編劇也穿插了一些感情戲份,讓我們一起來看看吧。
我們insert
Mary,並指定其為建明的lover
。
insert Character {
name:= "Mary",
eng_name:= "Mary",
lover:= lau,
actors:= (insert Actor{
name:= "鄭秀文",
eng_name:= "Sammi",
}),
};
update lau
set {
lover:= assert_single((select Character filter .name="Mary")),
};
我們insert
李心兒,並指定其為永仁的lover
。
insert Character {
name:= "李心兒",
lover:= chen,
actors:= (insert Actor{
name:= "陳慧琳",
eng_name:= "Kelly",
}),
};
update chen
set {
lover:= assert_single((select Character filter .name="李心兒")),
};
現在我們面臨了一個有趣的情形,永仁看起來有兩個lover
,但是我們的初始schema只設計了一個single link
的lover
。我們現在需要將這個single link
的lover
轉變為multi link
的lovers
。這其中其實包含了兩步的變更,第一步是將lover
重新命名為lovers
,第二步是將lovers
由single link
改為multi link
。
您可以選擇做兩次migration
,但其實EdgeDB相當聰明,大部份時間能夠猜中我們的意圖,讓我們試試用一步的migration
來完成這個變化吧。我們變更Character
如下:
type Character extending Person {
classic_lines: array<str>;
multi lovers: Character;
multi actors: Actor;
}
接著於命令列執行edgedb migration create
。
did you drop link 'lover' of object type 'default::Character'? [y,n,l,c,b,s,q,?]
> n
did you rename link 'lover' of object type 'default::Character' to 'lovers'? [y,n,l,c,b,s,q,?]
> y
did you convert link 'lovers' of object type 'default::Character' to 'multi' cardinality? [y,n,l,c,b,s,q,?]
> y
留意第一個選項我們選擇了n
,於是EdgeDB試著詢問我們。如果不是要drop
的話,是否是要rename
。如果是要rename
的話,是否由single link
改為multi link
。如此一來,我們原來於lover
中所指向的object
,在於命令列執行edgedb migrate
後也會一併帶到lovers
。
如果第一個選項我們選擇了y
,EdgeDB會認為我們想先drop
掉lover
,然候加上一個multi link
的lovers
。如此一來lovers
將會是空set
,我們需要在於命令列執行完edgedb migrate
後,手動將原來lover
所指向的object
加進來。
由這個例子可以知道,migration
時不一定只能選擇y
,應該視當下需求來決定。
由於此處進行了migration
,所以需要再一次設定global current_user_id
。
set global current_user_id:= (
select Police
filter .police_rank=PoliceRank.DCP limit 1).id;
最後我們insert
May,並將May加入到chen
的lovers
。
# end migration needs to be applied before running this query
insert Character{
name:= "May",
eng_name:= "May",
lovers:= chen,
actors:= (insert Actor{
name:= "蕭亞軒",
eng_name:= "Elva",
}),
};
update chen
set {
lovers+= assert_single((select Character filter .name="May")),
};
我們可以確認chen
的lovers
內確實有心兒及May。
select chen.lovers.name;
{'李心兒', 'May'}
detached
假設永仁覺得自己有太多lovers
,想利用update
幫他斷捨離,但卻發現有時候lovers
會被設為空set
,他百思不得其解,讓我們一起來看看永仁遇到的情況。永仁一共嘗試了下列五種query,只有query1會將lovers
設為空set
,query2~query5都可以成功將lovers
設定為心兒一人:
#❌
update Character filter .name="陳永仁"
set {lovers:= (select Character filter .name="李心兒")};
#✅
update chen
set {lovers:= (select Character filter .name="李心兒")};
#✅
with ch:= (select Character filter .name="陳永仁")
update ch
set {lovers:= (select Character filter .name="李心兒")};
#✅
update PoliceSpy filter .name="陳永仁"
set {lovers:= (select Character filter .name="李心兒")};
#✅
update Character filter .name="陳永仁"
set {lovers:= (select detached Character filter .name="李心兒")};
原來問題出在query1中,我們在update Character
的set(關鍵字)
內再次使用了select Character
。這個Character
將會是外面update Character filter .name="陳永仁"
語法中的EdgeDBset
,而不是Character
這個object type
。當想要在各種top-level EdgeQL statements(select
, insert
, update
及delete
)內再次引用同一個object type
時,需要使用detached
。
這是個很常見的錯誤,以上提供了四種修訂方法:
alias
,如chen
。with
區塊內,暫時命名一個變數,如ch
。update
時改使用其它object type
,如PoliceSpy
。detached
。insert
此場景的Scene
insert Scene {
title:= "我想做個好人",
detail:= "建明與永仁於天台相見,不料國平也趕到。永仁事先已報警,想持槍壓著" ++
"建明到樓下交予警方。不料,於進電梯時被國平擊斃,原來他也是韓琛安" ++
"裝於警隊的臥底。國平向建明表明身份,希望之後一起合作。但最終建明" ++
"選擇於電梯中殺死國平,並營造永仁與國平雙雙死於槍戰的假象。事後," ++
"心兒於葉校長遺物中發現永仁臥底檔案,恢復其警察身份,並由建明", ++
"代表行禮。"
who:= (select Police filter .name="林國平") union
(select PoliceSpy filter .name="林國平") union
{chen, lau},
`when`:= assert_single(
(
select FuzzyTime
filter .fuzzy_fmt="2002/11/27_15:00:00_ID"
)
),
where:= (select Location filter .name="天台"),
};
uuid
選取object
的技巧假設我們想選擇一開始建立的PoliceRank
為DCP
的Police object
,該怎麼寫query呢?
最簡單的方法應該是filter .name="test_DCP"
了吧,像是:
select Police filter .name="test_DCP";
但是假設我們只有該object
str
型態的id
的話,又該怎麼選取呢?您可能會寫出以下query:
with pid:= <str>(select Police filter .name="test_DCP").id,
select Police filter .id=<uuid>pid;
但是除了這種經典的寫法外,EdgeDB還提供了以下寫法:
with pid:= <str>(select Police filter .name="test_DCP").id,
select <Police><uuid>pid;
pid
此時為str
型態的uuid
,我們可以在前面使用<Police><uuid>
來casting
而取得object
。
當想要使用上述寫法並搭配shape construction
時,需加上()
,例如:
# ✅
with pid:= <str>(select Police filter .name="test_DCP").id,
select (<Police><uuid>pid) {*};
而下面這兩種寫法是不被允許的:
# ❌
with pid:= <str>(select Police filter .name="test_DCP").id,
select <Police><uuid>pid {*};
# ❌
with pid:= <str>(select Police filter .name="test_DCP").id,
select {*} <Police><uuid>pid;
另外,如果您已經有一個EdgeDBset
,也可以進行類似的操作,例如:
with pid:= (select Police filter .name="test_DCP").id,
select <Police>pid;
讓我們用上述技巧來刪除一開始建立的PoliceRank
為DCP
的Police object
,並reset
global
current_user_id
。
with pid:= <str>(select Police filter .name="test_DCP").id,
delete <Police><uuid>pid;
reset global current_user_id;
根據訪談,於拍攝時間只有華仔與編劇導演等少部份人知道,國平也是韓琛所派臥底,甚至連飾演國平的林家棟都是到最後一幕快開拍前才知道。當時他擔心前面的戲份是不是有演得不合劇情的地方,華仔說沒問題,他要的就是這種反差感。